---
title: SwiftUI
sidebar_title: SwiftUI
description: SwiftUI components for building native iOS interfaces with @expo/ui.
sourceCodeUrl: 'https://github.com/expo/expo/tree/main/packages/expo-ui'
packageName: '@expo/ui'
platforms: ['ios', 'tvos']
isBeta: true
hasVideoLink: true
---

import { BookOpen02Icon } from '@expo/styleguide-icons/outline/BookOpen02Icon';

import { APIInstallSection } from '~/components/plugins/InstallSection';
import { BoxLink } from '~/ui/components/BoxLink';
import { ContentSpotlight } from '~/ui/components/ContentSpotlight';
import { Tabs, Tab } from '~/ui/components/Tabs';
import { CODE } from '~/ui/components/Text';
import { VideoBoxLink } from '~/ui/components/VideoBoxLink';

> **important** **This library is currently in beta and subject to breaking changes.** It is not available in the Expo Go app &mdash; use [development builds](/develop/development-builds/introduction/) to try it out.

The SwiftUI components in `@expo/ui/swift-ui` allow you to build fully native iOS interfaces using SwiftUI from React Native.

## Installation

<APIInstallSection />

## Usage

Using a component from `@expo/ui/swift-ui` requires wrapping it in a [`Host`](#host) component. The `Host` is a container for SwiftUI views.

```tsx
import { Host, Button } from '@expo/ui/swift-ui';

export function SaveButton() {
  return (
    <Host style={{ flex: 1 }}>
      <Button variant="default">Save changes</Button>
    </Host>
  );
}
```

For an in-depth explanation of how `Host` works, see the following resources:

<BoxLink
  title="Expo UI guide for Swift UI"
  description={
    <>
      Learn about the basics of <CODE>@expo/ui/swift-ui</CODE>
    </>
  }
  href="/guides/expo-ui-swift-ui/"
  Icon={BookOpen02Icon}
/>

<VideoBoxLink
  videoId="2wXYLWz3YEQ"
  title="Expo UI iOS Liquid Glass Tutorial"
  description="Learn how to build real SwiftUI views in your React Native app with the new Expo UI."
/>

## Components

### BottomSheet

<Tabs>

<Tab label="iOS">
  <ContentSpotlight
    alt="BottomSheet component on iOS."
    src="/static/images/expo-ui/bottomsheet/ios.png"
    className="max-w-[240px]"
  />
</Tab>

  <Tab label="Code">
    ```tsx
    import { BottomSheet, Host, Text } from '@expo/ui/swift-ui';
    import { useWindowDimensions } from 'react-native';

    const { width } = useWindowDimensions();

    <Host style={{ position: 'absolute', width }}>
      <BottomSheet isOpened={isOpened} onIsOpenedChange={e => setIsOpened(e)}>
        <Text>Hello, world!</Text>
      </BottomSheet>
    </Host>
    ```
    *See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/view/sheet(ispresented:ondismiss:content:))*

  </Tab>
</Tabs>

### Button

> **Warning** The borderless variant is not available on Apple TV.

<Tabs>
  <Tab label="iOS">
    <ContentSpotlight
      alt="Button component on iOS."
      src="/static/images/expo-ui/button/ios.png"
      className="max-w-[480px]"
    />
  </Tab>
  <Tab label="Code">
    ```tsx
    import { Button, Host } from '@expo/ui/swift-ui';

    <Host style={{ flex: 1 }}>
      <Button
        variant="default"
        onPress={() => {
          setEditingProfile(true);
        }}>
        Edit profile
      </Button>
    </Host>
    ```
    *See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/button)*

  </Tab>
</Tabs>

### CircularProgress

<Tabs>
  <Tab label="iOS">
    <ContentSpotlight
      alt="CircularProgress component on iOS."
      src="/static/images/expo-ui/circularprogress/ios.png"
      className="max-w-[380px]"
    />
  </Tab>
  <Tab label="Code">
    ```tsx
    import { CircularProgress, Host } from '@expo/ui/swift-ui';

    <Host style={{ width: 300 }}>
      <CircularProgress progress={0.5} color="blue" />
    </Host>
    ```
    *See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/progressview)*

  </Tab>
</Tabs>

### ColorPicker

> **Warning** This component is not available on Apple TV.

<Tabs>

<Tab label="iOS">
  <ContentSpotlight
    alt="ColorPicker component on iOS."
    src="/static/images/expo-ui/colorpicker/ios.png"
    className="max-w-[240px]"
  />
</Tab>

  <Tab label="Code">
    ```tsx
    import { ColorPicker, Host } from '@expo/ui/swift-ui';

    <Host style={{ width: 400, height: 200 }}>
      <ColorPicker
        label="Select a color"
        selection={color}
        onValueChanged={setColor}
      />
    </Host>
    ```
    *See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/colorpicker)*

  </Tab>
</Tabs>

### ContextMenu

> **Note:** Also known as **DropdownMenu**.

<Tabs>
  <Tab label="iOS">
    <ContentSpotlight
      alt="ContextMenu component on iOS."
      src="/static/images/expo-ui/contextMenu/ios.png"
      className="max-w-[380px]"
    />
  </Tab>
  <Tab label="Code">
    ```tsx
    import { ContextMenu, Host } from '@expo/ui/swift-ui';

    <Host style={{ width: 150, height: 50 }}>
      <ContextMenu>
        <ContextMenu.Items>
          <Button
            systemImage="person.crop.circle.badge.xmark"
            onPress={() => console.log('Pressed1')}>
            Hello
          </Button>
          <Button
            variant="bordered"
            systemImage="heart"
            onPress={() => console.log('Pressed2')}>
            Love it
          </Button>
          <Picker
            label="Doggos"
            options={['very', 'veery', 'veeery', 'much']}
            variant="menu"
            selectedIndex={selectedIndex}
            onOptionSelected={({ nativeEvent: { index } }) => setSelectedIndex(index)}
          />
        </ContextMenu.Items>
        <ContextMenu.Trigger>
          <Button variant="bordered">
            Show Menu
          </Button>
        </ContextMenu.Trigger>
      </ContextMenu>
    </Host>
    ```
    *See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/view/contextmenu(menuitems:))*

  </Tab>
</Tabs>

### DateTimePicker (date)

> **Warning** This component is not available on Apple TV.

<Tabs>
  <Tab label="iOS">
    <ContentSpotlight
      alt="DateTimePicker (date) component on iOS."
      src="/static/images/expo-ui/datetimepicker/date/ios.png"
      className="max-w-[240px]"
    />
  </Tab>
  <Tab label="Code">
    ```tsx
    import { DateTimePicker, Host } from '@expo/ui/swift-ui';

    <Host matchContents>
      <DateTimePicker
        onDateSelected={date => {
          setSelectedDate(date);
        }}
        displayedComponents='date'
        initialDate={selectedDate.toISOString()}
        variant='wheel'
      />
    </Host>
    ```
    *See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/datepicker)*

  </Tab>
</Tabs>

### DateTimePicker (time)

> **Warning** This component is not available on Apple TV.

<Tabs>
  <Tab label="iOS">
    <ContentSpotlight
      alt="DateTimePicker (time) component on iOS."
      src="/static/images/expo-ui/datetimepicker/time/ios.png"
      className="max-w-[240px]"
    />
  </Tab>
  <Tab label="Code">
    ```tsx
    import { DateTimePicker, Host } from '@expo/ui/swift-ui';

    <Host matchContents>
      <DateTimePicker
        onDateSelected={date => {
          setSelectedDate(date);
        }}
        displayedComponents='hourAndMinute'
        initialDate={selectedDate.toISOString()}
        variant='wheel'
      />
    </Host>
    ```
    *See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/datepicker)*

  </Tab>
</Tabs>

### Gauge

> **Warning** This component is not available on Apple TV.

<Tabs>
  <Tab label="iOS">
    <ContentSpotlight
      alt="Gauge component on iOS."
      src="/static/images/expo-ui/gauge/ios.png"
      className="max-w-[240px]"
    />
  </Tab>

  <Tab label="Code">
    ```tsx
    import { Gauge, Host } from "@expo/ui/swift-ui";

    <Host matchContents>
      <Gauge
        max={{ value: 1, label: '1' }}
        min={{ value: 0, label: '0' }}
        current={{ value: 0.5 }}
        color={[
          PlatformColor('systemRed'),
          PlatformColor('systemOrange'),
          PlatformColor('systemYellow'),
          PlatformColor('systemGreen'),
        ]}
        type="circularCapacity"
      />
    </Host>
    ```
    *See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/gauge)*

  </Tab>
</Tabs>

### Host

A component that allows you to put the other `@expo/ui/swift-ui` components in React Native. It acts like [`<svg>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/svg) for DOM, [`<Canvas>`](https://shopify.github.io/react-native-skia/docs/canvas/overview/) for [`react-native-skia`](https://shopify.github.io/react-native-skia/), which underlying uses [`UIHostingController`](https://developer.apple.com/documentation/swiftui/uihostingcontroller) to render the SwiftUI views in UIKit.

Since the `Host` component is a React Native [`View`](https://reactnative.dev/docs/view), you can pass the [`style`](https://reactnative.dev/docs/style) prop to it or `matchContents` prop to make the `Host` component match the contents' size.

```tsx Wrapping Button in Host
import { Button, Host } from '@expo/ui/swift-ui';

function Example() {
  return (
    <Host matchContents>
      <Button
        onPress={() => {
          console.log('Pressed');
        }}>
        Click
      </Button>
    </Host>
  );
}
```

```tsx Host with flexbox and VStack
import { Button, Host, VStack, Text } from '@expo/ui/swift-ui';

function Example() {
  return (
    <Host style={{ flex: 1 }}>
      <VStack spacing={8}>
        <Text>Hello, world!</Text>
        <Button
          onPress={() => {
            console.log('Pressed');
          }}>
          Click
        </Button>
      </VStack>
    </Host>
  );
}
```

### LinearProgress

<Tabs>
  <Tab label="iOS">
    <ContentSpotlight
      alt="LinearProgress component on iOS."
      src="/static/images/expo-ui/linearprogress/ios.png"
      className="max-w-[380px]"
    />
  </Tab>
  <Tab label="Code">
    ```tsx
    import { LinearProgress, Host } from '@expo/ui/swift-ui';

    <Host style={{ width: 300 }}>
      <LinearProgress progress={0.5} color="red" />
    </Host>
    ```
    *See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/progressview)*

  </Tab>
</Tabs>

### List

<Tabs>
  <Tab label="iOS">
    <ContentSpotlight
      alt="List component on iOS."
      src="/static/images/expo-ui/list/ios.png"
      className="max-w-[380px]"
    />
  </Tab>
  <Tab label="Code">
    ```tsx
    import { Host, List } from '@expo/ui/swift-ui';

    <Host style={{ flex: 1 }}>
      <List
        scrollEnabled={false}
        editModeEnabled={editModeEnabled}
        onSelectionChange={(items) => alert(`indexes of selected items: ${items.join(', ')}`)}
        moveEnabled={moveEnabled}
        onMoveItem={(from, to) => alert(`moved item at index ${from} to index ${to}`)}
        onDeleteItem={(item) => alert(`deleted item at index: ${item}`)}
        listStyle='automatic'
        deleteEnabled={deleteEnabled}
        selectEnabled={selectEnabled}>
        {data.map((item, index) => (
          <LabelPrimitive key={index} title={item.text} systemImage={item.systemImage} color={color} />
        ))}
      </List>
    </Host>
    ```
    *See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/list)*

  </Tab>
</Tabs>

### Picker (segmented)

<Tabs>
  <Tab label="iOS">
    <ContentSpotlight
      alt="Picker component on iOS."
      src="/static/images/expo-ui/segmentedPicker/ios.png"
      className="max-w-[480px]"
    />
  </Tab>
  <Tab label="Code">
    ```tsx
    import { Host, Picker } from '@expo/ui/swift-ui';

    <Host matchContents>
      <Picker
        options={['$', '$$', '$$$', '$$$$']}
        selectedIndex={selectedIndex}
        onOptionSelected={({ nativeEvent: { index } }) => {
          setSelectedIndex(index);
        }}
        variant="segmented"
      />
    </Host>
    ```
    *See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/picker#Styling-pickers)*

  </Tab>
</Tabs>

### Picker (wheel)

> **Warning** The wheel variant is not available on Apple TV.

<Tabs>
  <Tab label="iOS">
    <ContentSpotlight
      alt="Picker component on iOS."
      src="/static/images/expo-ui/menuPicker/ios.png"
      className="max-w-[480px]"
    />
  </Tab>
  <Tab label="Code">
    ```tsx
    import { Host, Picker } from '@expo/ui/swift-ui';

    <Host style={{ height: 100 }}>
      <Picker
        options={['$', '$$', '$$$', '$$$$']}
        selectedIndex={selectedIndex}
        onOptionSelected={({ nativeEvent: { index } }) => {
          setSelectedIndex(index);
        }}
        variant="wheel"
      />
    </Host>
    ```
    *See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/pickerstyle/wheel)*

  </Tab>
</Tabs>

### Slider

> **Warning** This component is not available on Apple TV.

<Tabs>
  <Tab label="iOS">
    <ContentSpotlight
      alt="Slider component on iOS."
      src="/static/images/expo-ui/slider/ios.png"
      className="max-w-[480px]"
    />
  </Tab>
  <Tab label="Code">
    ```tsx
    import { Host, Slider } from '@expo/ui/swift-ui';

    <Host style={{ minHeight: 60 }}>
      <Slider
        value={value}
        onValueChange={(value) => {
          setValue(value);
        }}
      />
    </Host>
    ```
    *See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/slider)*

  </Tab>
</Tabs>

### Switch (toggle)

> **Note:** Also known as **Toggle**.

<Tabs>
  <Tab label="iOS">
    <ContentSpotlight
      alt="Switch component on iOS."
      src="/static/images/expo-ui/switch/ios.png"
      className="max-w-[480px]"
    />
  </Tab>
  <Tab label="Code">
    ```tsx
    import { Host, Switch } from '@expo/ui/swift-ui';

    <Host matchContents>
      <Switch
        checked={checked}
        onValueChange={checked => {
          setChecked(checked);
        }}
        color="#ff0000"
        label="Play music"
        variant="switch"
      />
    </Host>
    ```
    *See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/toggle)*

  </Tab>
</Tabs>

### Switch (checkbox)

<Tabs>
  <Tab label="iOS">
    <ContentSpotlight
      alt="Picker component on iOS."
      src="/static/images/expo-ui/checkbox/ios.png"
      className="max-w-[480px]"
    />
  </Tab>
  <Tab label="Code">
    ```tsx
    import { Host, Switch } from '@expo/ui/swift-ui';

    <Host matchContents>
      <Switch
        checked={checked}
        onValueChange={checked => {
          setChecked(checked);
        }}
        label="Play music"
        variant="checkbox"
      />
    </Host>
    ```
    *See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/toggle)*

  </Tab>
</Tabs>

### TextField

<Tabs>
  <Tab label="iOS">
    <ContentSpotlight
      alt="TextField component on iOS."
      src="/static/images/expo-ui/textinput/ios.png"
      className="max-w-[320px]"
    />
  </Tab>
  <Tab label="Code">
    ```tsx
    import { Host, TextField } from '@expo/ui/swift-ui';

    <Host matchContents>
      <TextField autocorrection={false} defaultValue="A single line text input" onChangeText={setValue} />
    </Host>
    ```
    *See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/textfield)*

  </Tab>
</Tabs>

### More

Expo UI is still in active development. We continue to add more functionality and may change the API. Some examples in the docs may not be up to date. If you want to see the latest changes, check the [examples](https://github.com/expo/expo/tree/main/apps/native-component-list/src/screens/UI).

## API

Full documentation is not yet available. Use TypeScript types to explore the API.

```ts
// Import from the SwiftUI package
import { BottomSheet } from '@expo/ui/swift-ui';
```
